llms-py 2.0.8__py3-none-any.whl → 2.0.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llms.py +144 -13
- llms_py-2.0.10.data/data/index.html +80 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/llms.json +16 -10
- llms_py-2.0.10.data/data/ui/Avatar.mjs +28 -0
- llms_py-2.0.10.data/data/ui/Brand.mjs +23 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/ChatPrompt.mjs +101 -69
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/Main.mjs +43 -183
- llms_py-2.0.10.data/data/ui/ModelSelector.mjs +29 -0
- llms_py-2.0.10.data/data/ui/ProviderStatus.mjs +105 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/Recents.mjs +2 -1
- llms_py-2.0.10.data/data/ui/SettingsDialog.mjs +374 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/Sidebar.mjs +11 -27
- llms_py-2.0.10.data/data/ui/SignIn.mjs +64 -0
- llms_py-2.0.10.data/data/ui/SystemPromptEditor.mjs +31 -0
- llms_py-2.0.10.data/data/ui/SystemPromptSelector.mjs +36 -0
- llms_py-2.0.10.data/data/ui/Welcome.mjs +8 -0
- llms_py-2.0.10.data/data/ui/ai.mjs +80 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/app.css +76 -10
- llms_py-2.0.10.data/data/ui/lib/servicestack-vue.mjs +37 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/markdown.mjs +9 -2
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/tailwind.input.css +13 -4
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/threadStore.mjs +2 -2
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/typography.css +109 -1
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/utils.mjs +8 -2
- {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/METADATA +124 -39
- llms_py-2.0.10.dist-info/RECORD +40 -0
- llms_py-2.0.8.data/data/index.html +0 -64
- llms_py-2.0.8.data/data/ui/lib/servicestack-vue.min.mjs +0 -37
- llms_py-2.0.8.dist-info/RECORD +0 -30
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/requirements.txt +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/App.mjs +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/fav.svg +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/highlight.min.mjs +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/idb.min.mjs +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/marked.min.mjs +0 -0
- /llms_py-2.0.8.data/data/ui/lib/servicestack-client.min.mjs → /llms_py-2.0.10.data/data/ui/lib/servicestack-client.mjs +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/vue-router.min.mjs +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui/lib/vue.min.mjs +0 -0
- {llms_py-2.0.8.data → llms_py-2.0.10.data}/data/ui.json +0 -0
- {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/WHEEL +0 -0
- {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/entry_points.txt +0 -0
- {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/licenses/LICENSE +0 -0
- {llms_py-2.0.8.dist-info → llms_py-2.0.10.dist-info}/top_level.txt +0 -0
llms.py
CHANGED
|
@@ -14,6 +14,7 @@ import mimetypes
|
|
|
14
14
|
import traceback
|
|
15
15
|
import sys
|
|
16
16
|
import site
|
|
17
|
+
from urllib.parse import parse_qs
|
|
17
18
|
|
|
18
19
|
import aiohttp
|
|
19
20
|
from aiohttp import web
|
|
@@ -21,7 +22,7 @@ from aiohttp import web
|
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
from importlib import resources # Py≥3.9 (pip install importlib_resources for 3.7/3.8)
|
|
23
24
|
|
|
24
|
-
VERSION = "2.0.
|
|
25
|
+
VERSION = "2.0.10"
|
|
25
26
|
_ROOT = None
|
|
26
27
|
g_config_path = None
|
|
27
28
|
g_ui_path = None
|
|
@@ -63,7 +64,8 @@ def chat_summary(chat):
|
|
|
63
64
|
elif 'file' in item:
|
|
64
65
|
if 'file_data' in item['file']:
|
|
65
66
|
data = item['file']['file_data']
|
|
66
|
-
|
|
67
|
+
prefix = data.split(',', 1)[0]
|
|
68
|
+
item['file']['file_data'] = prefix + f",({len(data) - len(prefix)})"
|
|
67
69
|
return json.dumps(clone, indent=2)
|
|
68
70
|
|
|
69
71
|
def gemini_chat_summary(gemini_chat):
|
|
@@ -89,6 +91,60 @@ def is_url(url):
|
|
|
89
91
|
def get_filename(file):
|
|
90
92
|
return file.rsplit('/',1)[1] if '/' in file else 'file'
|
|
91
93
|
|
|
94
|
+
def parse_args_params(args_str):
|
|
95
|
+
"""Parse URL-encoded parameters and return a dictionary."""
|
|
96
|
+
if not args_str:
|
|
97
|
+
return {}
|
|
98
|
+
|
|
99
|
+
# Parse the URL-encoded string
|
|
100
|
+
parsed = parse_qs(args_str, keep_blank_values=True)
|
|
101
|
+
|
|
102
|
+
# Convert to simple dict with single values (not lists)
|
|
103
|
+
result = {}
|
|
104
|
+
for key, values in parsed.items():
|
|
105
|
+
if len(values) == 1:
|
|
106
|
+
value = values[0]
|
|
107
|
+
# Try to convert to appropriate types
|
|
108
|
+
if value.lower() == 'true':
|
|
109
|
+
result[key] = True
|
|
110
|
+
elif value.lower() == 'false':
|
|
111
|
+
result[key] = False
|
|
112
|
+
elif value.isdigit():
|
|
113
|
+
result[key] = int(value)
|
|
114
|
+
else:
|
|
115
|
+
try:
|
|
116
|
+
# Try to parse as float
|
|
117
|
+
result[key] = float(value)
|
|
118
|
+
except ValueError:
|
|
119
|
+
# Keep as string
|
|
120
|
+
result[key] = value
|
|
121
|
+
else:
|
|
122
|
+
# Multiple values, keep as list
|
|
123
|
+
result[key] = values
|
|
124
|
+
|
|
125
|
+
return result
|
|
126
|
+
|
|
127
|
+
def apply_args_to_chat(chat, args_params):
|
|
128
|
+
"""Apply parsed arguments to the chat request."""
|
|
129
|
+
if not args_params:
|
|
130
|
+
return chat
|
|
131
|
+
|
|
132
|
+
# Apply each parameter to the chat request
|
|
133
|
+
for key, value in args_params.items():
|
|
134
|
+
if isinstance(value, str):
|
|
135
|
+
if key == 'stop':
|
|
136
|
+
if ',' in value:
|
|
137
|
+
value = value.split(',')
|
|
138
|
+
elif key == 'max_completion_tokens' or key == 'max_tokens' or key == 'n' or key == 'seed' or key == 'top_logprobs':
|
|
139
|
+
value = int(value)
|
|
140
|
+
elif key == 'temperature' or key == 'top_p' or key == 'frequency_penalty' or key == 'presence_penalty':
|
|
141
|
+
value = float(value)
|
|
142
|
+
elif key == 'store' or key == 'logprobs' or key == 'enable_thinking' or key == 'parallel_tool_calls' or key == 'stream':
|
|
143
|
+
value = bool(value)
|
|
144
|
+
chat[key] = value
|
|
145
|
+
|
|
146
|
+
return chat
|
|
147
|
+
|
|
92
148
|
def is_base_64(data):
|
|
93
149
|
try:
|
|
94
150
|
base64.b64decode(data)
|
|
@@ -190,8 +246,9 @@ async def process_chat(chat):
|
|
|
190
246
|
content = f.read()
|
|
191
247
|
file['filename'] = get_filename(url)
|
|
192
248
|
file['file_data'] = f"data:{mimetype};base64,{base64.b64encode(content).decode('utf-8')}"
|
|
193
|
-
elif
|
|
194
|
-
|
|
249
|
+
elif url.startswith('data:'):
|
|
250
|
+
if 'filename' not in file:
|
|
251
|
+
file['filename'] = 'file'
|
|
195
252
|
pass # use base64 data as-is
|
|
196
253
|
else:
|
|
197
254
|
raise Exception(f"Invalid file: {url}")
|
|
@@ -232,6 +289,25 @@ class OpenAiProvider:
|
|
|
232
289
|
if api_key is not None:
|
|
233
290
|
self.headers["Authorization"] = f"Bearer {api_key}"
|
|
234
291
|
|
|
292
|
+
self.frequency_penalty = float(kwargs['frequency_penalty']) if 'frequency_penalty' in kwargs else None
|
|
293
|
+
self.max_completion_tokens = int(kwargs['max_completion_tokens']) if 'max_completion_tokens' in kwargs else None
|
|
294
|
+
self.n = int(kwargs['n']) if 'n' in kwargs else None
|
|
295
|
+
self.parallel_tool_calls = bool(kwargs['parallel_tool_calls']) if 'parallel_tool_calls' in kwargs else None
|
|
296
|
+
self.presence_penalty = float(kwargs['presence_penalty']) if 'presence_penalty' in kwargs else None
|
|
297
|
+
self.prompt_cache_key = kwargs['prompt_cache_key'] if 'prompt_cache_key' in kwargs else None
|
|
298
|
+
self.reasoning_effort = kwargs['reasoning_effort'] if 'reasoning_effort' in kwargs else None
|
|
299
|
+
self.safety_identifier = kwargs['safety_identifier'] if 'safety_identifier' in kwargs else None
|
|
300
|
+
self.seed = int(kwargs['seed']) if 'seed' in kwargs else None
|
|
301
|
+
self.service_tier = kwargs['service_tier'] if 'service_tier' in kwargs else None
|
|
302
|
+
self.stop = kwargs['stop'] if 'stop' in kwargs else None
|
|
303
|
+
self.store = bool(kwargs['store']) if 'store' in kwargs else None
|
|
304
|
+
self.temperature = float(kwargs['temperature']) if 'temperature' in kwargs else None
|
|
305
|
+
self.top_logprobs = int(kwargs['top_logprobs']) if 'top_logprobs' in kwargs else None
|
|
306
|
+
self.top_p = float(kwargs['top_p']) if 'top_p' in kwargs else None
|
|
307
|
+
self.verbosity = kwargs['verbosity'] if 'verbosity' in kwargs else None
|
|
308
|
+
self.stream = bool(kwargs['stream']) if 'stream' in kwargs else None
|
|
309
|
+
self.enable_thinking = bool(kwargs['enable_thinking']) if 'enable_thinking' in kwargs else None
|
|
310
|
+
|
|
235
311
|
@classmethod
|
|
236
312
|
def test(cls, base_url=None, api_key=None, models={}, **kwargs):
|
|
237
313
|
return base_url is not None and api_key is not None and len(models) > 0
|
|
@@ -247,6 +323,41 @@ class OpenAiProvider:
|
|
|
247
323
|
# with open(os.path.join(os.path.dirname(__file__), 'chat.wip.json'), "w") as f:
|
|
248
324
|
# f.write(json.dumps(chat, indent=2))
|
|
249
325
|
|
|
326
|
+
if self.frequency_penalty is not None:
|
|
327
|
+
chat['frequency_penalty'] = self.frequency_penalty
|
|
328
|
+
if self.max_completion_tokens is not None:
|
|
329
|
+
chat['max_completion_tokens'] = self.max_completion_tokens
|
|
330
|
+
if self.n is not None:
|
|
331
|
+
chat['n'] = self.n
|
|
332
|
+
if self.parallel_tool_calls is not None:
|
|
333
|
+
chat['parallel_tool_calls'] = self.parallel_tool_calls
|
|
334
|
+
if self.presence_penalty is not None:
|
|
335
|
+
chat['presence_penalty'] = self.presence_penalty
|
|
336
|
+
if self.prompt_cache_key is not None:
|
|
337
|
+
chat['prompt_cache_key'] = self.prompt_cache_key
|
|
338
|
+
if self.reasoning_effort is not None:
|
|
339
|
+
chat['reasoning_effort'] = self.reasoning_effort
|
|
340
|
+
if self.safety_identifier is not None:
|
|
341
|
+
chat['safety_identifier'] = self.safety_identifier
|
|
342
|
+
if self.seed is not None:
|
|
343
|
+
chat['seed'] = self.seed
|
|
344
|
+
if self.service_tier is not None:
|
|
345
|
+
chat['service_tier'] = self.service_tier
|
|
346
|
+
if self.stop is not None:
|
|
347
|
+
chat['stop'] = self.stop
|
|
348
|
+
if self.store is not None:
|
|
349
|
+
chat['store'] = self.store
|
|
350
|
+
if self.temperature is not None:
|
|
351
|
+
chat['temperature'] = self.temperature
|
|
352
|
+
if self.top_logprobs is not None:
|
|
353
|
+
chat['top_logprobs'] = self.top_logprobs
|
|
354
|
+
if self.top_p is not None:
|
|
355
|
+
chat['top_p'] = self.top_p
|
|
356
|
+
if self.verbosity is not None:
|
|
357
|
+
chat['verbosity'] = self.verbosity
|
|
358
|
+
if self.enable_thinking is not None:
|
|
359
|
+
chat['enable_thinking'] = self.enable_thinking
|
|
360
|
+
|
|
250
361
|
chat = await process_chat(chat)
|
|
251
362
|
_log(f"POST {self.chat_url}")
|
|
252
363
|
_log(chat_summary(chat))
|
|
@@ -333,7 +444,14 @@ class GoogleProvider(OpenAiProvider):
|
|
|
333
444
|
async with aiohttp.ClientSession() as session:
|
|
334
445
|
for message in chat['messages']:
|
|
335
446
|
if message['role'] == 'system':
|
|
336
|
-
|
|
447
|
+
content = message['content']
|
|
448
|
+
if isinstance(content, list):
|
|
449
|
+
for item in content:
|
|
450
|
+
if 'text' in item:
|
|
451
|
+
system_prompt = item['text']
|
|
452
|
+
break
|
|
453
|
+
elif isinstance(content, str):
|
|
454
|
+
system_prompt = content
|
|
337
455
|
elif 'content' in message:
|
|
338
456
|
if isinstance(message['content'], list):
|
|
339
457
|
parts = []
|
|
@@ -409,7 +527,7 @@ class GoogleProvider(OpenAiProvider):
|
|
|
409
527
|
# Add system instruction if present
|
|
410
528
|
if system_prompt is not None:
|
|
411
529
|
gemini_chat['systemInstruction'] = {
|
|
412
|
-
"parts": [{"text": system_prompt
|
|
530
|
+
"parts": [{"text": system_prompt}]
|
|
413
531
|
}
|
|
414
532
|
|
|
415
533
|
if 'stop' in chat:
|
|
@@ -537,10 +655,14 @@ async def chat_completion(chat):
|
|
|
537
655
|
# If we get here, all providers failed
|
|
538
656
|
raise first_exception
|
|
539
657
|
|
|
540
|
-
async def cli_chat(chat, image=None, audio=None, file=None, raw=False):
|
|
658
|
+
async def cli_chat(chat, image=None, audio=None, file=None, args=None, raw=False):
|
|
541
659
|
if g_default_model:
|
|
542
660
|
chat['model'] = g_default_model
|
|
543
661
|
|
|
662
|
+
# Apply args parameters to chat request
|
|
663
|
+
if args:
|
|
664
|
+
chat = apply_args_to_chat(chat, args)
|
|
665
|
+
|
|
544
666
|
# process_chat downloads the image, just adding the reference here
|
|
545
667
|
if image is not None:
|
|
546
668
|
first_message = None
|
|
@@ -925,6 +1047,7 @@ def main():
|
|
|
925
1047
|
parser.add_argument('--image', default=None, help='Image input to use in chat completion')
|
|
926
1048
|
parser.add_argument('--audio', default=None, help='Audio input to use in chat completion')
|
|
927
1049
|
parser.add_argument('--file', default=None, help='File input to use in chat completion')
|
|
1050
|
+
parser.add_argument('--args', default=None, help='URL-encoded parameters to add to chat request (e.g. "temperature=0.7&seed=111")', metavar='PARAMS')
|
|
928
1051
|
parser.add_argument('--raw', action='store_true', help='Return raw AI JSON response')
|
|
929
1052
|
|
|
930
1053
|
parser.add_argument('--list', action='store_true', help='Show list of enabled providers and their models (alias ls provider?)')
|
|
@@ -1141,7 +1264,7 @@ def main():
|
|
|
1141
1264
|
app.router.add_route('*', '/{tail:.*}', index_handler)
|
|
1142
1265
|
|
|
1143
1266
|
if os.path.exists(g_ui_path):
|
|
1144
|
-
async def
|
|
1267
|
+
async def ui_config_handler(request):
|
|
1145
1268
|
with open(g_ui_path, "r") as f:
|
|
1146
1269
|
ui = json.load(f)
|
|
1147
1270
|
if 'defaults' not in ui:
|
|
@@ -1153,7 +1276,7 @@ def main():
|
|
|
1153
1276
|
"disabled": disabled
|
|
1154
1277
|
}
|
|
1155
1278
|
return web.json_response(ui)
|
|
1156
|
-
app.router.add_get('/
|
|
1279
|
+
app.router.add_get('/config', ui_config_handler)
|
|
1157
1280
|
|
|
1158
1281
|
print(f"Starting server on port {port}...")
|
|
1159
1282
|
web.run_app(app, host='0.0.0.0', port=port)
|
|
@@ -1256,13 +1379,21 @@ def main():
|
|
|
1256
1379
|
if len(extra_args) > 0:
|
|
1257
1380
|
prompt = ' '.join(extra_args)
|
|
1258
1381
|
# replace content of last message if exists, else add
|
|
1259
|
-
last_msg = chat['messages'][-1]
|
|
1260
|
-
if last_msg['role'] == 'user':
|
|
1261
|
-
last_msg['content']
|
|
1382
|
+
last_msg = chat['messages'][-1] if 'messages' in chat else None
|
|
1383
|
+
if last_msg and last_msg['role'] == 'user':
|
|
1384
|
+
if isinstance(last_msg['content'], list):
|
|
1385
|
+
last_msg['content'][-1]['text'] = prompt
|
|
1386
|
+
else:
|
|
1387
|
+
last_msg['content'] = prompt
|
|
1262
1388
|
else:
|
|
1263
1389
|
chat['messages'].append({'role': 'user', 'content': prompt})
|
|
1264
1390
|
|
|
1265
|
-
|
|
1391
|
+
# Parse args parameters if provided
|
|
1392
|
+
args = None
|
|
1393
|
+
if cli_args.args is not None:
|
|
1394
|
+
args = parse_args_params(cli_args.args)
|
|
1395
|
+
|
|
1396
|
+
asyncio.run(cli_chat(chat, image=cli_args.image, audio=cli_args.audio, file=cli_args.file, args=args, raw=cli_args.raw))
|
|
1266
1397
|
exit(0)
|
|
1267
1398
|
except Exception as e:
|
|
1268
1399
|
print(f"{cli_args.logprefix}Error: {e}")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>llms.py</title>
|
|
4
|
+
<link rel="stylesheet" href="/ui/typography.css">
|
|
5
|
+
<link rel="stylesheet" href="/ui/app.css">
|
|
6
|
+
<link rel="icon" type="image/svg" href="/ui/fav.svg">
|
|
7
|
+
<style>
|
|
8
|
+
[type='button'],button[type='submit']{cursor:pointer}
|
|
9
|
+
[type='checkbox'].switch:checked:hover,
|
|
10
|
+
[type='checkbox'].switch:checked:focus,
|
|
11
|
+
[type='checkbox'].switch:checked,
|
|
12
|
+
[type='checkbox'].switch:focus,
|
|
13
|
+
[type='checkbox'].switch
|
|
14
|
+
{
|
|
15
|
+
border: none;
|
|
16
|
+
background: none;
|
|
17
|
+
outline: none;
|
|
18
|
+
box-shadow: none;
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
22
|
+
</head>
|
|
23
|
+
<script type="importmap">
|
|
24
|
+
{
|
|
25
|
+
"imports": {
|
|
26
|
+
"vue": "/ui/lib/vue.min.mjs",
|
|
27
|
+
"vue-router": "/ui/lib/vue-router.min.mjs",
|
|
28
|
+
"@servicestack/client": "/ui/lib/servicestack-client.mjs",
|
|
29
|
+
"@servicestack/vue": "/ui/lib/servicestack-vue.mjs",
|
|
30
|
+
"idb": "/ui/lib/idb.min.mjs",
|
|
31
|
+
"marked": "/ui/lib/marked.min.mjs",
|
|
32
|
+
"highlight.js": "/ui/lib/highlight.min.mjs"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
<body>
|
|
37
|
+
<div id="app"></div>
|
|
38
|
+
</body>
|
|
39
|
+
<script type="module">
|
|
40
|
+
import { createApp, defineAsyncComponent } from 'vue'
|
|
41
|
+
import { createWebHistory, createRouter } from "vue-router"
|
|
42
|
+
import ServiceStackVue from "@servicestack/vue"
|
|
43
|
+
import App from '/ui/App.mjs'
|
|
44
|
+
import ai from '/ui/ai.mjs'
|
|
45
|
+
import SettingsDialog from '/ui/SettingsDialog.mjs'
|
|
46
|
+
|
|
47
|
+
const { config, models } = await ai.init()
|
|
48
|
+
const MainComponent = defineAsyncComponent(() => import(ai.base + '/ui/Main.mjs'))
|
|
49
|
+
const RecentsComponent = defineAsyncComponent(() => import(ai.base + '/ui/Recents.mjs'))
|
|
50
|
+
|
|
51
|
+
const Components = {
|
|
52
|
+
SettingsDialog,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const routes = [
|
|
56
|
+
{ path: '/', component: MainComponent },
|
|
57
|
+
{ path: '/c/:id', component: MainComponent },
|
|
58
|
+
{ path: '/recents', component: RecentsComponent },
|
|
59
|
+
{ path: '/:fallback(.*)*', component: MainComponent }
|
|
60
|
+
]
|
|
61
|
+
routes.forEach(r => r.path = ai.base + r.path)
|
|
62
|
+
const router = createRouter({
|
|
63
|
+
history: createWebHistory(),
|
|
64
|
+
routes,
|
|
65
|
+
})
|
|
66
|
+
const app = createApp(App, { config, models })
|
|
67
|
+
app.use(router)
|
|
68
|
+
app.use(ServiceStackVue)
|
|
69
|
+
app.provide('ai', ai)
|
|
70
|
+
app.provide('config', config)
|
|
71
|
+
app.provide('models', models)
|
|
72
|
+
Object.keys(Components).forEach(name => {
|
|
73
|
+
app.component(name, Components[name])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
window.ai = app.config.globalProperties.$ai = ai
|
|
77
|
+
|
|
78
|
+
app.mount('#app')
|
|
79
|
+
</script>
|
|
80
|
+
</html>
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
"messages": [
|
|
10
10
|
{
|
|
11
11
|
"role": "user",
|
|
12
|
-
"content":
|
|
12
|
+
"content": [
|
|
13
|
+
{
|
|
14
|
+
"type": "text",
|
|
15
|
+
"text": ""
|
|
16
|
+
}
|
|
17
|
+
]
|
|
13
18
|
}
|
|
14
19
|
]
|
|
15
20
|
},
|
|
@@ -90,10 +95,8 @@
|
|
|
90
95
|
"deepseek-r1:671b": "deepseek/deepseek-r1-0528:free",
|
|
91
96
|
"gemini-2.0-flash": "google/gemini-2.0-flash-exp:free",
|
|
92
97
|
"glm-4.5-air": "z-ai/glm-4.5-air:free",
|
|
93
|
-
"grok-4-fast": "x-ai/grok-4-fast:free",
|
|
94
98
|
"mai-ds-r1": "microsoft/mai-ds-r1:free",
|
|
95
99
|
"llama3.3:70b": "meta-llama/llama-3.3-70b-instruct:free",
|
|
96
|
-
"kimi-k2": "moonshotai/kimi-k2:free",
|
|
97
100
|
"nemotron-nano:9b": "nvidia/nemotron-nano-9b-v2:free",
|
|
98
101
|
"deepseek-r1-distill-llama:70b": "deepseek/deepseek-r1-distill-llama-70b:free",
|
|
99
102
|
"gpt-oss:20b": "openai/gpt-oss-20b:free",
|
|
@@ -102,7 +105,6 @@
|
|
|
102
105
|
"devstral-small": "mistralai/devstral-small-2505:free",
|
|
103
106
|
"venice-uncensored:24b": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free",
|
|
104
107
|
"llama3.3:8b": "meta-llama/llama-3.3-8b-instruct:free",
|
|
105
|
-
"llama3.1:405b": "meta-llama/llama-3.1-405b-instruct:free",
|
|
106
108
|
"kimi-dev:72b": "moonshotai/kimi-dev-72b:free",
|
|
107
109
|
"gemma3:27b": "google/gemma-3-27b-it:free",
|
|
108
110
|
"qwen3-coder": "qwen/qwen3-coder:free",
|
|
@@ -171,7 +173,7 @@
|
|
|
171
173
|
}
|
|
172
174
|
},
|
|
173
175
|
"ollama": {
|
|
174
|
-
"enabled":
|
|
176
|
+
"enabled": true,
|
|
175
177
|
"type": "OllamaProvider",
|
|
176
178
|
"base_url": "http://localhost:11434",
|
|
177
179
|
"models": {},
|
|
@@ -389,7 +391,8 @@
|
|
|
389
391
|
"qwen2.5-vl:7b": "qwen2.5-vl-7b-instruct",
|
|
390
392
|
"qwen2.5-vl:3b": "qwen2.5-vl-3b-instruct",
|
|
391
393
|
"qwen2.5-omni:7b": "qwen2.5-omni-7b"
|
|
392
|
-
}
|
|
394
|
+
},
|
|
395
|
+
"enable_thinking": false
|
|
393
396
|
},
|
|
394
397
|
"z.ai": {
|
|
395
398
|
"enabled": false,
|
|
@@ -404,7 +407,8 @@
|
|
|
404
407
|
"glm-4.5-airx": "glm-4.5-airx",
|
|
405
408
|
"glm-4.5-flash": "glm-4.5-flash",
|
|
406
409
|
"glm-4:32b": "glm-4-32b-0414-128k"
|
|
407
|
-
}
|
|
410
|
+
},
|
|
411
|
+
"temperature": 0.7
|
|
408
412
|
},
|
|
409
413
|
"mistral": {
|
|
410
414
|
"enabled": false,
|
|
@@ -417,20 +421,22 @@
|
|
|
417
421
|
"devstral-medium": "devstral-medium-2507",
|
|
418
422
|
"codestral:22b": "codestral-latest",
|
|
419
423
|
"mistral-ocr": "mistral-ocr-latest",
|
|
420
|
-
"voxtral-mini": "voxtral-mini-latest",
|
|
421
424
|
"mistral-small3.2:24b": "mistral-small-latest",
|
|
422
425
|
"magistral-small": "magistral-small-latest",
|
|
423
426
|
"devstral-small": "devstral-small-2507",
|
|
424
427
|
"voxtral-small": "voxtral-small-latest",
|
|
428
|
+
"voxtral-mini": "voxtral-mini-latest",
|
|
429
|
+
"codestral-embed": "codestral-embed-2505",
|
|
430
|
+
"mistral-embed": "mistral-embed",
|
|
425
431
|
"mistral-large:123b": "mistral-large-latest",
|
|
426
432
|
"pixtral-large:124b": "pixtral-large-latest",
|
|
427
433
|
"pixtral:12b": "pixtral-12b",
|
|
428
|
-
"mistral-nemo:12b": "mistral-nemo",
|
|
434
|
+
"mistral-nemo:12b": "open-mistral-nemo",
|
|
429
435
|
"mistral-saba": "mistral-saba-latest",
|
|
430
436
|
"mistral:7b": "open-mistral-7b",
|
|
431
437
|
"mixtral:8x7b": "open-mixtral-8x7b",
|
|
432
438
|
"mixtral:8x22b": "open-mixtral-8x22b",
|
|
433
|
-
"ministral:8b": "ministral-
|
|
439
|
+
"ministral:8b": "ministral-8b-latest",
|
|
434
440
|
"ministral:3b": "ministral-3b-latest"
|
|
435
441
|
}
|
|
436
442
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { computed, inject } from "vue"
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
template:`
|
|
5
|
+
<div v-if="$ai.auth?.profileUrl" :title="authTitle">
|
|
6
|
+
<img :src="$ai.auth.profileUrl" class="size-8 rounded-full" />
|
|
7
|
+
</div>
|
|
8
|
+
`,
|
|
9
|
+
setup() {
|
|
10
|
+
const ai = inject('ai')
|
|
11
|
+
const authTitle = computed(() => {
|
|
12
|
+
if (!ai.auth) return ''
|
|
13
|
+
const { userId, userName, displayName, bearerToken, roles } = ai.auth
|
|
14
|
+
const name = userName || displayName
|
|
15
|
+
const prefix = roles && roles.includes('Admin') ? 'Admin' : 'Name'
|
|
16
|
+
const sb = [
|
|
17
|
+
name ? `${prefix}: ${name}` : '',
|
|
18
|
+
`API Key: ${bearerToken}`,
|
|
19
|
+
`${userId}`,
|
|
20
|
+
]
|
|
21
|
+
return sb.filter(x => x).join('\n')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
authTitle,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
template:`
|
|
3
|
+
<div class="flex-shrink-0 px-4 py-4 border-b border-gray-200 bg-white min-h-16 select-none">
|
|
4
|
+
<div class="flex items-center justify-between">
|
|
5
|
+
<button type="button"
|
|
6
|
+
@click="$emit('home')"
|
|
7
|
+
class="text-lg font-semibold text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
|
|
8
|
+
title="Go back to initial state"
|
|
9
|
+
>
|
|
10
|
+
History
|
|
11
|
+
</button>
|
|
12
|
+
<button type="button"
|
|
13
|
+
@click="$emit('new')"
|
|
14
|
+
class="text-gray-900 hover:text-blue-600 focus:outline-none transition-colors"
|
|
15
|
+
title="New Chat"
|
|
16
|
+
>
|
|
17
|
+
<svg class="size-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></g></svg>
|
|
18
|
+
</button>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
`,
|
|
22
|
+
emits:['home','new'],
|
|
23
|
+
}
|